package com.ruoyi.comparison.utils;

import org.apache.poi.xwpf.usermodel.*;

import java.io.*;
import java.text.DecimalFormat;
import java.util.*;

/**
 * @author tree
 * @date 2023/11/7 9:56
 */
public class TenderCompare {
    //招标文件
    private XWPFDocument biddingDocument;
    //招标文件路径
    private String biddingFilePath;
    //未处理的左侧投标文件
    private XWPFDocument leftCompBidding;
    //左侧竞标文件路径
    private String leftCompBiddingFilePath;
    //右侧竞标文件路径
    private String rightComBiddingFilePath;
    //未处理的右侧投标文件
    private XWPFDocument rightCompBidding;
    //处理后的竞标文件URL、重复的关键词及其对应的第一次出现的页面
    private HashMap<String,Object> fileDuplicatesPageMap;
    //定义连续重复的字数
    private int continuousWordNum = 11;
    //重复的关键词
    private List<String> duplicatedWords;

    private static String newDocDirName ;

    public TenderCompare(String leftCompBidding, String rightCompBidding , int continuousWordNum, String biddingFilePath, String taskName){
        this.continuousWordNum = continuousWordNum;
        this.biddingFilePath = biddingFilePath;
        this.leftCompBiddingFilePath = leftCompBidding;
        this.rightComBiddingFilePath = rightCompBidding;
        newDocDirName = taskName + UUID.randomUUID().toString().substring(0,4);
        FileInputStream biddingFIS = null;
        FileInputStream leftCompFIS = null;
        FileInputStream rightCompFIS = null;
        try{
            biddingFIS = new FileInputStream(biddingFilePath);
            this.biddingDocument = new XWPFDocument(biddingFIS);
            leftCompFIS = new FileInputStream(leftCompBidding);
            this.leftCompBidding = new XWPFDocument(leftCompFIS);
            rightCompFIS = new FileInputStream(rightCompBidding);
            this.rightCompBidding = new XWPFDocument(rightCompFIS);
            String docText1 = getDocParaText(this.leftCompBidding);
            String docText2 = getDocParaText(this.rightCompBidding);
            String docText3 = getDocParaText(this.biddingDocument);
            Tender tender = new Tender(docText1, docText2, this.continuousWordNum, docText3);
            this.duplicatedWords = tender.findAllCommonSubstrings(docText1, docText2, this.continuousWordNum, docText3);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            this.close(biddingFIS);
            this.close(leftCompFIS);
            this.close(rightCompFIS);
        }
    }

    public TenderCompare(){
        this.continuousWordNum = 11;
        this.biddingFilePath = "";
        this.leftCompBiddingFilePath = "";
        this.rightComBiddingFilePath = "";
        this.duplicatedWords = new LinkedList<String>();
    }

    public String getBiddingFilePath() {
        return biddingFilePath;
    }
    public String getLeftCompBiddingFilePath(){
        return leftCompBiddingFilePath;
    }

    public String getRightComBiddingFilePath(){
        return rightComBiddingFilePath;
    }

    public int getContinuousWordNum() {
        return continuousWordNum;
    }

    public List<String> getDuplicatedWords() {
        return duplicatedWords;
    }

    public void setBiddingDocument(XWPFDocument biddingDocument) {
        this.biddingDocument = biddingDocument;
    }

    public void setLeftCompBiddingFilePath(String leftCompBiddingFilePath) {
        this.leftCompBiddingFilePath = leftCompBiddingFilePath;
    }

    public void setRightComBiddingFilePath(String rightComBiddingFilePath) {
        this.rightComBiddingFilePath = rightComBiddingFilePath;
    }

    public void setBiddingFilePath(String biddingFilePath) {
        this.biddingFilePath = biddingFilePath;
    }

    public void setContinuousWordNum(int continuousWordNum) {
        this.continuousWordNum = continuousWordNum;
    }

    public void setDuplicatedWords(List<String> duplicatedWords) {
        this.duplicatedWords = duplicatedWords;
    }

    public void setLeftCompBidding(XWPFDocument leftCompBidding) {
        this.leftCompBidding = leftCompBidding;
    }

    public XWPFDocument getBiddingDocument() {
        return biddingDocument;
    }

    public void setFileDuplicatesPageMap(HashMap<String, Object> fileDuplicatesPageMap) {
        this.fileDuplicatesPageMap = fileDuplicatesPageMap;
    }

    public XWPFDocument getLeftCompBidding() {
        return leftCompBidding;
    }

    public void setRightCompBidding(XWPFDocument rightCompBidding) {
        this.rightCompBidding = rightCompBidding;
    }

    public XWPFDocument getRightCompBidding() {
        return rightCompBidding;
    }

    public HashMap<String, Object> redAndNewDoc(){
        HashMap<String, Object> keyFileMap= new HashMap<String ,Object>();
        try {
            for (String dup : this.duplicatedWords) {
                HashMap<String ,String> fileMap = new HashMap<>();
                FileInputStream fis1Temp = new FileInputStream(this.leftCompBiddingFilePath);
                FileInputStream fis2Temp = new FileInputStream(this.rightComBiddingFilePath);
                XWPFDocument doc1Temp = new XWPFDocument(fis1Temp);
                XWPFDocument doc2Temp = new XWPFDocument(fis2Temp);
                String outputLeftFilePath = newFileName(this.leftCompBiddingFilePath, this.rightComBiddingFilePath, dup);
                String outputRightFilePath = newFileName(this.rightComBiddingFilePath, this.leftCompBiddingFilePath, dup);
                redProgramListTableCell(doc1Temp, doc2Temp);
                redAllDocument(doc1Temp, dup);
                redAllDocument(doc2Temp, dup);
                saveToFile(outputLeftFilePath, doc1Temp);
                saveToFile(outputRightFilePath, doc2Temp);
                fileMap.put(outputLeftFilePath,outputRightFilePath);
                keyFileMap.put(dup, fileMap);
                close(fis1Temp);
                close(fis2Temp);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {

        }
        return keyFileMap;
    }

    public void createProjectListDoc( XWPFTable xwpfTable, String path) throws Exception {
        try {
            /*
            CTTblLayoutType type = xwpfTable.getCTTbl().getTblPr().addNewTblLayout();
            type.setType(STTblLayoutType.FIXED);

             */
            /*
            CTTblWidth copyWidth = xwpfTable.getCTTbl().addNewTblPr().addNewTblW();
            //默认是AUTO，这里改成DXA
            copyWidth.setType(STTblWidth.AUTO);
            copyWidth.setW(BigInteger.valueOf(0));

             */
            XWPFDocument doc = new XWPFDocument();
            XWPFParagraph paragraph = doc.createParagraph(); // 创建段落
            XWPFRun run = paragraph.createRun(); // 创建段落文本
            // 设置文本
            run.setText("重复的工程量清单");
            run.setFontSize(20);
            run.setBold(true);
            XWPFTable table = doc.createTable();
            doc.setTable(0, xwpfTable);
            /*
            CTTblWidth width = doc.getTables().get(0).getCTTbl().addNewTblPr().addNewTblW();
            //默认是AUTO，这里改成DXA
            width.setType(STTblWidth.AUTO);
            width.setW(BigInteger.valueOf(0));

             */
            table.setWidth("100%");
            saveToFile(path, doc);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public HashMap<String, Object> redAndNewDoc1(){
        HashMap<String, Object> keyFileMap= new HashMap<String ,Object>();
        try {
            for (String dup : this.duplicatedWords) {
                HashMap<String ,String> fileMap = new HashMap<>();
                FileInputStream fis1Temp = new FileInputStream(this.leftCompBiddingFilePath);
                FileInputStream fis2Temp = new FileInputStream(this.rightComBiddingFilePath);
                XWPFDocument doc1Temp = new XWPFDocument(fis1Temp);
                XWPFDocument doc2Temp = new XWPFDocument(fis2Temp);
                String outputLeftFilePath = newFileName(this.leftCompBiddingFilePath, this.rightComBiddingFilePath, dup);
                String outputRightFilePath = newFileName(this.rightComBiddingFilePath, this.leftCompBiddingFilePath, dup);
                redProgramListTableCell(doc1Temp, doc2Temp);
                redDoc(doc1Temp, dup);

                redDoc(doc2Temp, dup);
                saveToFile(outputLeftFilePath, doc1Temp);
                saveToFile(outputRightFilePath, doc2Temp);
                fileMap.put(outputLeftFilePath,outputRightFilePath);
                keyFileMap.put(dup, fileMap);
                close(fis1Temp);
                close(fis2Temp);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {

        }
        return keyFileMap;
    }

    public double calculateSimilarity(){
        String docText1 = getDocParaText(this.leftCompBidding);
        String docText2 = getDocParaText(this.rightCompBidding);
        return calculateJaccardSimilarity(docText1, docText2);
    }
    private void redAllDocument(XWPFDocument document, String keyWord){
        List<Integer> lengthList = new ArrayList<Integer>();
        List<String> runsTextList = new ArrayList<String>();
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        int lenTemp = 0;
        for (int i = 0; i < paragraphs.size(); i++) {
            XWPFParagraph paragraph = paragraphs.get(i);
            String paragraphText = paragraph.getText();
            lenTemp += paragraphText.length();
            lengthList.add(lenTemp);
            runsTextList.add(paragraphText);
        }
        String docText = getDocParaText(document);
        String docTextTemp = docText;
        //定义关键词在整个由段落组成的长文本中首次出现和结束的下标
        int firstIndex = 0, endIndex = 0;
        //定义关键词开始段落和结束段落
        int startPara = 0, endPara = 0;
        while(docTextTemp.contains(keyWord)){
            firstIndex = docTextTemp.indexOf(keyWord);
            endIndex = firstIndex + keyWord.length();
            startPara = binaryFind(lengthList, startPara, firstIndex + 1);
            endPara = binaryFind(lengthList, startPara, endIndex);
            //关键词跨越多个段落
            if(endPara > startPara){
                String startParaKey = docText.substring(firstIndex, lengthList.get(startPara));
                String endParaKey = docText.substring(lengthList.get(endPara - 1) , endIndex);
                XWPFParagraph  xwpfStartParagra = paragraphs.get(startPara);
                XWPFParagraph  xwpfEndParagra = paragraphs.get(endPara);
                //标红首尾段落
                redTextPara(xwpfStartParagra, startParaKey, 0, true);
                redTextPara(xwpfEndParagra, endParaKey, 0, true);
                //标红中间段落
                for(int i = startPara + 1; i <= endPara - 1; i++){
                    redTotalParagrah(paragraphs.get(i));
                }
            }
            //关键词在同一个段落
            else{
                XWPFParagraph  xwpfStartParagra = paragraphs.get(startPara);
                redTextPara(xwpfStartParagra, keyWord, 0, false);
            }
            docTextTemp = docTextTemp.substring(firstIndex + 1);
        }
    }

    private void redTotalParagrah(XWPFParagraph xwpfParagraph){
        for(XWPFRun run : xwpfParagraph.getRuns()){
            run.setColor("FF0000");
        }
    }

    /**
     * 计算两个文本的相似度(Jaccard算法)
     * @param strA
     * @param strB
     * @return
     */
    public double calculateJaccardSimilarity(String strA, String strB) {
        // 将字符串转换为字符集合
//        System.out.println("原字符串："+strA);
        Set<Character> setA = new HashSet<>();
        for (char c : strA.toCharArray()) {
            setA.add(c);
//            System.out.println("拆分的字符串：" + c);
        }

        Set<Character> setB = new HashSet<>();
        for (char c : strB.toCharArray()) {
            setB.add(c);
        }

        // 计算交集和并集的大小
        Set<Character> intersection = new HashSet<>(setA);
        intersection.retainAll(setB);

        Set<Character> union = new HashSet<>(setA);
        union.addAll(setB);

        // 计算Jaccard相似度
        double result = (double) intersection.size() / union.size();

        // 格式化返回值为6位小数点
        DecimalFormat decimalFormat = new DecimalFormat("#.######");
        String formattedResult = decimalFormat.format(result);
        return Double.parseDouble(formattedResult);
    }
    /**
     * 查找竞标文件相同的地方(排除招标文件中出现的关键词)
     * @return
     */
    public List<String> findDuplicates(String leftCompBiddingFilePath, String rightComBiddingFilePath, int cnum, String biddingFilePath){
        FileInputStream biddingFis = null, leftBis = null, rightBis = null;
        List<String> duplicates = new LinkedList<String>();
        try{
            biddingFis = new FileInputStream(biddingFilePath);
            leftBis = new FileInputStream(leftCompBiddingFilePath);
            rightBis = new FileInputStream(rightComBiddingFilePath);
            XWPFDocument biddingDoc = new XWPFDocument(biddingFis);
            XWPFDocument leftDoc = new XWPFDocument(leftBis);
            XWPFDocument rightDoc = new XWPFDocument(rightBis);
            String biddingDocText = getDocParaText(biddingDoc);
            String leftDocText = getDocParaText(leftDoc);
            String rightDocText = getDocParaText(rightDoc);
            Tender tender = new Tender(leftDocText, rightDocText, cnum, biddingDocText);
            //duplicates = tender.findDuplicates();

        }catch (Exception e){
            e.printStackTrace();
        }finally{
            close(biddingFis);
            close(leftBis);
            close(rightBis);
        }
        return duplicates;
    }

    /**
     * 获取标书所有段落文本
     * @param doc
     * @return 标书所有段落文本
     */
    private String getDocParaText(XWPFDocument doc){
        StringBuffer docText = new StringBuffer();
        for(XWPFParagraph paragraph:doc.getParagraphs()){
            docText.append(paragraph.getText());
        }
        return docText.toString();
    }

    /**
     * 对文件进行标红
     * @param document
     * @param keyWord
     */
    public void redDoc(XWPFDocument document, String keyWord){
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (int i = 0; i < paragraphs.size();i++ ) {
            XWPFParagraph paragraph = paragraphs.get(i);
            String paragraphText = paragraph.getText();
            if(paragraphText.contains((keyWord))){
                redTextOnePara(paragraph, keyWord, 0);
            }
            else if(i <= paragraphs.size()-2) {
                XWPFParagraph paragraphNext = paragraphs.get(i + 1);
                redTwoContinuousPara(paragraph, paragraphNext, keyWord);
            }
        }
    }
    public HashMap<String,String> newRedProgramListDoc() throws Exception {
        HashMap<String,String> listFileMap = new HashMap<>();
        String outputLeftFilePath = newFileName(this.leftCompBiddingFilePath, this.rightComBiddingFilePath,"红工程量清单");
        String outputRightFilePath = newFileName(this.rightComBiddingFilePath, this.leftCompBiddingFilePath,"红工程量清单");
        List<XWPFTable> tablesA = this.leftCompBidding.getTables();
        List<XWPFTableRow> tableRowsA = null;
        List<XWPFTableRow> tableRowsB = null;
        List<XWPFTableCell> tableCellsA = null, tableCellsB = null;
        List<XWPFTable> tablesB = this.rightCompBidding.getTables();
        String tableTextA = "", tableTextB = "";
        XWPFTable listTableA = null, listTableB = null;
        int repeatCount = 0;//定义重复的行数，加上表头，行业 >1 认为重复
        for (int ia = 0; ia < tablesA.size(); ia++) {
            XWPFTable xwpfTableA = tablesA.get(ia);
            tableRowsA = xwpfTableA.getRows();
            tableTextA = xwpfTableA.getText();
            int aProgramNameNum = 0, aUnitNum = 0, aQuantityNum = 0, aPriceNum = 0;
            Boolean foundPriceA = false;
            int aProgramCellsSize = 0;//定义关于工程量清单包含的列数
            if (tableTextA.contains("名称") && tableTextA.contains("单位") && tableTextA.contains("量") && tableTextA.contains("单价")) {
                if(tableTextA.contains("合价") || tableTextA.contains("合计") || tableTextA.contains("元")) {
                    for (int ja = 0; ja < tableRowsA.size(); ja++) {
                        XWPFTableRow rowa = xwpfTableA.getRow(ja);
                        tableCellsA = rowa.getTableCells();
                        String aProgram = "", aUnit = "", aQuantity = "", aPrice = "";
                        if (!foundPriceA) {
                            for (int ka = 0; ka < tableCellsA.size(); ka++) {
                                XWPFTableCell cella = rowa.getCell(ka);
                                //如果没找着，那就找

                                if (cella.getText().contains("名称")) {
                                    aProgramNameNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("单位")) {
                                    aUnitNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("量")) {
                                    aQuantityNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("单价")) {
                                    aPriceNum = ka;
                                    continue;
                                }
                                if (aProgramNameNum < aQuantityNum && aQuantityNum < aPriceNum) {
                                    //找到项目名称、单位、数量、单价所在的列号
                                    aProgramCellsSize = tableCellsA.size();
                                    foundPriceA = true;
                                    listTableA = xwpfTableA;
                                    break;
                                }
                            }
                        }
                        if (foundPriceA && aProgramCellsSize == tableCellsA.size()) {
                            aProgram = rowa.getCell(aProgramNameNum).getText();
                            //System.out.println("a名称:" + aProgram);
                            aQuantity = rowa.getCell(aQuantityNum).getText();
                            //System.out.println("a数量:" + aQuantity);
                            aPrice = rowa.getCell(aPriceNum).getText();
                            //System.out.println("a单价:" + aPrice);
                            String aPriceContent = removeRNT(aPrice);
                            aPriceContent = trimAllWhitespace(aPriceContent);
                            String aQuantityContent = removeRNT(aQuantity);
                            aQuantityContent = trimAllWhitespace(aQuantityContent);
                            //如果某行的价格为空或者数量为空，直接跳过该行
                            if (aPriceContent.isEmpty() || aQuantityContent.isEmpty()) {
                                continue;
                            }
                            for (int ib = 0; ib < tablesB.size(); ib++) {
                                tableTextB = tablesB.get(ib).getText();
                                XWPFTable xwpfTableB = tablesB.get(ib);
                                tableRowsB = xwpfTableB.getRows();
                                int bProgramNameNum = 0, bUnitNum = 0, bQuantityNum = 0, bPriceNum = 0;
                                boolean foundPriceB = false;
                                int bProgramCellsSize = 0;//定义b工程量清单包含明细项的列数
                                if (tableTextB.contains("名称") && tableTextB.contains("单位") && tableTextB.contains("量") && tableTextB.contains("单价")) {
                                    if (tableTextB.contains("合价") || tableTextB.contains("合计") || tableTextB.contains("元")) {
                                        for (int jb = 0; jb < tableRowsB.size(); jb++) {
                                            XWPFTableRow rowb = xwpfTableB.getRow(jb);
                                            tableCellsB = rowb.getTableCells();
                                            String bProgram = "", bUnit = "", bQuantity = "", bPrice = "";
                                            if (!foundPriceB) {
                                                for (int kb = 0; kb < tableCellsB.size(); kb++) {
                                                    XWPFTableCell cellb = rowb.getCell(kb);
                                                    // System.out.println("B表格中第" + jb + "行" + "第" + kb + "列文本" + cellb.getText());
                                                    if (cellb.getText().contains("名称")) {
                                                        bProgramNameNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("单位")) {
                                                        bUnitNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("量")) {
                                                        bQuantityNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("单价")) {
                                                        bPriceNum = kb;
                                                        continue;
                                                    }
                                                    if (bProgramNameNum < bQuantityNum && bQuantityNum < bPriceNum) {
                                                        //找到项目名称、单位、数量、单价所在的列号,去除了单位列号顺序判断
                                                        foundPriceB = true;
                                                        listTableB = xwpfTableB;
                                                        bProgramCellsSize = tableCellsB.size();
                                                        break;
                                                    }
                                                }
                                            }
                                            if (foundPriceB && tableCellsB.size() == bProgramCellsSize) {
                                                bProgram = rowb.getCell(bProgramNameNum).getText();
                                                //System.out.println("b名称:" + bProgram);
                                                bQuantity = rowb.getCell(bQuantityNum).getText();
                                                //System.out.println("b数量:" + bQuantity);
                                                bPrice = rowb.getCell(bPriceNum).getText();
                                                //System.out.println("b单价:" + bPrice);
                                                String bPriceContent = removeRNT(bPrice);
                                                bPriceContent = trimAllWhitespace(bPriceContent);
                                                String bQuantityContent = removeRNT(bQuantity);
                                                bQuantityContent = trimAllWhitespace(bQuantityContent);
                                                //如果表格B的某行单价或数量为空，也是直接跳过该行
                                                if (bPriceContent.isEmpty() || bQuantityContent.isEmpty()) {
                                                    continue;
                                                }
                                    /*
                                    System.out.println("A表格中第" + ja + "行 " + "第" + aProgramNameNum + "列工程文本:" + aProgram
                                            +  " 价格文本:" + bPrice);
                                    System.out.println("B表格中第" + jb + "行 " + "第" + bProgramNameNum + "列工程文本:" + bProgram
                                            +  " 价格文本:" + bPrice);
                                    System.out.println("aProgram.isEmpty():" + aProgram.isEmpty());

                                     */
                                                if (!aProgram.isEmpty() && !bProgram.isEmpty()) {
                                                    //项目名称相同或者相似度>=90%，价格相同，被视为重复，
                                                    if (aProgram.equals(bProgram) || calculateJaccardSimilarity(aProgram, bProgram) >= 0.9) {
                                                        if (!aPrice.isEmpty() && !bPrice.isEmpty() && aPrice.equals(bPrice)) {
                                                            repeatCount ++;
                                                            rowa.getCell(aPriceNum).setColor("FF0000");
                                                            rowa.getCell(aProgramNameNum).setColor("FF0000");
                                                            rowa.getCell(aQuantityNum).setColor("FF0000");
                                                            rowa.getCell(aPriceNum).setColor("FF0000");
                                                            rowb.getCell(bPriceNum).setColor("FF0000");
                                                            rowb.getCell(bProgramNameNum).setColor("FF0000");
                                                            rowb.getCell(bQuantityNum).setColor("FF0000");
                                                            rowb.getCell(bPriceNum).setColor("FF0000");

                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if(foundPriceA)//工程量清单只有一个 找到就跳出
                        break;
                }
            }
        }
        if(repeatCount >= 2){
            createProjectListDoc(listTableA, outputLeftFilePath);
            createProjectListDoc(listTableB, outputRightFilePath);
            listFileMap.put(outputLeftFilePath,outputRightFilePath);
        }
        return listFileMap;

    }

    private void redProgramListTableCell(XWPFDocument docA ,XWPFDocument docB) throws Exception {
        String outputLeftFilePath = newFileName(this.leftCompBiddingFilePath, this.rightComBiddingFilePath,"红工程量清单");
        String outputRightFilePath = newFileName(this.rightComBiddingFilePath, this.leftCompBiddingFilePath,"红工程量清单");
        List<XWPFTable> tablesA = docA.getTables();
        List<XWPFTableRow> tableRowsA = null;
        List<XWPFTableRow> tableRowsB = null;
        List<XWPFTableCell> tableCellsA = null, tableCellsB = null;
        List<XWPFTable> tablesB = docB.getTables();
        String tableTextA = "", tableTextB = "";
        XWPFTable xwpfTableA = null, xwpfTableB = null;
        for (int ia = 0; ia < tablesA.size(); ia++) {
            xwpfTableA = tablesA.get(ia);
            tableRowsA = xwpfTableA.getRows();
            tableTextA = xwpfTableA.getText();
            int aProgramNameNum = 0, aUnitNum = 0, aQuantityNum = 0, aPriceNum = 0;
            Boolean foundPriceA = false;
            int aProgramCellsSize = 0;//定义关于工程量清单包含的列数
            if (tableTextA.contains("名称") && tableTextA.contains("单位") && tableTextA.contains("量") && tableTextA.contains("单价")) {
                if(tableTextA.contains("合价") || tableTextA.contains("合计") || tableTextA.contains("元")) {
                    for (int ja = 0; ja < tableRowsA.size(); ja++) {
                        XWPFTableRow rowa = xwpfTableA.getRow(ja);
                        tableCellsA = rowa.getTableCells();
                        String aProgram = "", aUnit = "", aQuantity = "", aPrice = "";
                        if (!foundPriceA) {
                            for (int ka = 0; ka < tableCellsA.size(); ka++) {
                                XWPFTableCell cella = rowa.getCell(ka);
                                //如果没找着，那就找

                                if (cella.getText().contains("名称")) {
                                    aProgramNameNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("单位")) {
                                    aUnitNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("量")) {
                                    aQuantityNum = ka;
                                    continue;
                                }
                                if (cella.getText().contains("单价")) {
                                    aPriceNum = ka;
                                    continue;
                                }
                                if (aProgramNameNum < aQuantityNum && aQuantityNum < aPriceNum) {
                                    //找到项目名称、单位、数量、单价所在的列号
                                    aProgramCellsSize = tableCellsA.size();
                                    foundPriceA = true;
                                    break;
                                }
                            }
                        }
                        if (foundPriceA && aProgramCellsSize == tableCellsA.size()) {
                            aProgram = rowa.getCell(aProgramNameNum).getText();
                            //System.out.println("a名称:" + aProgram);
                            aQuantity = rowa.getCell(aQuantityNum).getText();
                            //System.out.println("a数量:" + aQuantity);
                            aPrice = rowa.getCell(aPriceNum).getText();
                            //System.out.println("a单价:" + aPrice);
                            String aPriceContent = removeRNT(aPrice);
                            aPriceContent = trimAllWhitespace(aPriceContent);
                            String aQuantityContent = removeRNT(aQuantity);
                            aQuantityContent = trimAllWhitespace(aQuantityContent);
                            //如果某行的价格为空或者数量为空，直接跳过该行
                            if (aPriceContent.isEmpty() || aQuantityContent.isEmpty()) {
                                continue;
                            }
                            for (int ib = 0; ib < tablesB.size(); ib++) {
                                Boolean isListRepeat = false;//定义两个文件中工程量清单是否重复标志位
                                tableTextB = tablesB.get(ib).getText();
                                xwpfTableB = tablesB.get(ib);
                                tableRowsB = xwpfTableB.getRows();
                                int bProgramNameNum = 0, bUnitNum = 0, bQuantityNum = 0, bPriceNum = 0;
                                boolean foundPriceB = false;
                                int bProgramCellsSize = 0;//定义b工程量清单包含明细项的列数
                                if (tableTextB.contains("名称") && tableTextB.contains("单位") && tableTextB.contains("量") && tableTextB.contains("单价")) {
                                    if (tableTextB.contains("合价") || tableTextB.contains("合计") || tableTextB.contains("元")) {
                                        for (int jb = 0; jb < tableRowsB.size(); jb++) {
                                            XWPFTableRow rowb = xwpfTableB.getRow(jb);
                                            tableCellsB = rowb.getTableCells();
                                            String bProgram = "", bUnit = "", bQuantity = "", bPrice = "";
                                            if (!foundPriceB) {
                                                for (int kb = 0; kb < tableCellsB.size(); kb++) {
                                                    XWPFTableCell cellb = rowb.getCell(kb);
                                                    // System.out.println("B表格中第" + jb + "行" + "第" + kb + "列文本" + cellb.getText());
                                                    if (cellb.getText().contains("名称")) {
                                                        bProgramNameNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("单位")) {
                                                        bUnitNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("量")) {
                                                        bQuantityNum = kb;
                                                        continue;
                                                    }
                                                    if (cellb.getText().contains("单价")) {
                                                        bPriceNum = kb;
                                                        continue;
                                                    }
                                                    if (bProgramNameNum < bQuantityNum && bQuantityNum < bPriceNum) {
                                                        //找到项目名称、单位、数量、单价所在的列号,去除了单位列号顺序判断
                                                        foundPriceB = true;
                                                        bProgramCellsSize = tableCellsB.size();
                                                        break;
                                                    }
                                                }
                                            }
                                            if (foundPriceB && tableCellsB.size() == bProgramCellsSize) {
                                                bProgram = rowb.getCell(bProgramNameNum).getText();
                                                //System.out.println("b名称:" + bProgram);
                                                bQuantity = rowb.getCell(bQuantityNum).getText();
                                                //System.out.println("b数量:" + bQuantity);
                                                bPrice = rowb.getCell(bPriceNum).getText();
                                                //System.out.println("b单价:" + bPrice);
                                                String bPriceContent = removeRNT(bPrice);
                                                bPriceContent = trimAllWhitespace(bPriceContent);
                                                String bQuantityContent = removeRNT(bQuantity);
                                                bQuantityContent = trimAllWhitespace(bQuantityContent);
                                                //如果表格B的某行单价或数量为空，也是直接跳过该行
                                                if (bPriceContent.isEmpty() || bQuantityContent.isEmpty()) {
                                                    continue;
                                                }
                                    /*
                                    System.out.println("A表格中第" + ja + "行 " + "第" + aProgramNameNum + "列工程文本:" + aProgram
                                            +  " 价格文本:" + bPrice);
                                    System.out.println("B表格中第" + jb + "行 " + "第" + bProgramNameNum + "列工程文本:" + bProgram
                                            +  " 价格文本:" + bPrice);
                                    System.out.println("aProgram.isEmpty():" + aProgram.isEmpty());

                                     */
                                                if (!aProgram.isEmpty() && !bProgram.isEmpty()) {
                                                    //项目名称相同或者相似度>=90%，价格相同，被视为重复，
                                                    if (aProgram.equals(bProgram) || calculateJaccardSimilarity(aProgram, bProgram) >= 0.9) {
                                                        if (!aPrice.isEmpty() && !bPrice.isEmpty() && aPrice.equals(bPrice)) {
                                                            isListRepeat = true;
                                                            rowa.getCell(aPriceNum).setColor("FF0000");
                                                            rowa.getCell(aProgramNameNum).setColor("FF0000");
                                                            rowa.getCell(aQuantityNum).setColor("FF0000");
                                                            rowa.getCell(aPriceNum).setColor("FF0000");
                                                            rowb.getCell(bPriceNum).setColor("FF0000");
                                                            rowb.getCell(bProgramNameNum).setColor("FF0000");
                                                            rowb.getCell(bQuantityNum).setColor("FF0000");
                                                            rowb.getCell(bPriceNum).setColor("FF0000");

                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        if(isListRepeat){
                                            createProjectListDoc(xwpfTableA, outputLeftFilePath);
                                            createProjectListDoc(xwpfTableB, outputRightFilePath);
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
            }
        }
    }
    /**
     * 在正则表达式中\s表示所有的空格：    匹配任何空白字符，包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。
     * 使用正则表达式,移除换行符（且不移除空格）
     *
     * @param originalStr 原始字符串
     * @return 移除换行\r、回车\n、制表\t符的字符串
     */
    public static String removeRNT(String originalStr) {
        if (originalStr == null || originalStr.isEmpty()) {
            return originalStr;
        }
        return originalStr.replaceAll("[\t\n\r]","");
    }

    /**
     * 移除字符串中所有的空白格（包含换行\r、回车\n、制表\t符）
     *
     * @param str 原始串
     * @return 无空格后的串
     */
    private String trimAllWhitespace(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        int len = str.length();
        StringBuilder sb = new StringBuilder(str.length());
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (!Character.isWhitespace(c)) {
                sb.append(c);
            }
        }
        return sb.toString();
    }
    private void redTwoContinuousPara(XWPFParagraph upXwpfParagraph, XWPFParagraph downXwpfParagraph, String keyWord){
        //上个段落文本
        String upParaText = upXwpfParagraph.getText();
        //下个段落文本
        String downParaText = downXwpfParagraph.getText();
        //StringBuffer twoParaTextBuffer = new StringBuffer(upParaText).append(downParaText);
        //融合两个段落文本
        String twoParaText = upParaText + downParaText;
        String upKey = "", downKey = "";
        int firstIndex = 0;
        if(!upParaText.contains(keyWord) && !downParaText.contains(keyWord) &&twoParaText.contains(keyWord)){
            //找到上个段落第一次出现关键词的下标
            firstIndex = twoParaText.indexOf(keyWord);
            upKey = upParaText.substring(firstIndex);
            downKey = keyWord.substring(upKey.length());
            redTextOnePara(upXwpfParagraph, upKey, firstIndex - 1);
            redTextOnePara(downXwpfParagraph, downKey, 0);
        }
    }
    /**
     *
     * @param xwpfParagraph
     * @param keyWord
     */
    private void redTextPara(XWPFParagraph xwpfParagraph, String keyWord, int searchBegin, boolean onlyRedFirst){
        String paraText = xwpfParagraph.getText().replaceAll("\\r|\\n", "");
        List<XWPFRun> runs = xwpfParagraph.getRuns();
        String paraTemp = paraText;
        if (paraTemp.contains(keyWord) && keyWord.length()!=0) {
            /*
            if(paraTemp.contains("rd 转换成 html ，再通过jsp来"))
                System.out.println("xwpfParagraph.getParagraphText() paraText:" + paraText);

             */
            int lentmp = 0;
            List<Integer> lengthList = new ArrayList<Integer>();
            List<String> runsTextList = new ArrayList<String>();
            for(int i = 0; i < runs.size(); i++){
                lentmp += runs.get(i).getText(runs.get(i).getTextPosition()) == null ? 0 : runs.get(i).getText(0).length();
                lengthList.add(lentmp);
                runsTextList.add(runs.get(i).getText(0));
            }
            /*
            for (int len = 0; len < lengthList.size(); len++)
                System.out.println(("lengthLis runsTextList: " + len + " 长度: " + lengthList.get(len)) +"  text:" + runsTextList.get(len));

             */
            /*
            if(keyWord.equals("rd 转换成 html ，再通过jsp来")){
                System.out.println("xwpfParagraph.getText() paraText:" + xwpfParagraph.getText());
                for (int len = 0; len < lengthList.size(); len++)
                    System.out.println(("lengthLis runsTextList: " + len + " 长度: " + lengthList.get(len)) +"  text:" + runsTextList.get(len));

            }

             */
            //定义关键字所在初始Run和结束Run序号
            int startRunPos = 0, endRunPos = 0;
            //由于分割开始run和结束run标红新增的run，为了及时更新startRunPos和 endRunPos
            int runPlus = 0;
            int runKeyCount = 0;
            //某个run的结尾字符是下一个关键词的头部标志
            Boolean runLastIsKeyBefore = false;//
            /*无论关键词是否在同一个run，关键词之前的文本和之后的文本都要根据情况做处理，而不是简单新增和删除
               1.关键词在同一个run，
               1.1处理本run中第一个关键词，
               1.2处理完本run最后一个关键词后，下一个关键词的头部依然在本run
               此时处理到关键词结束就停止处理，以后的字符交给下一个循环处理，且本次循环不删除当前run，
               此时用betweenTwoKeyText把位于两个key之间的文本提前取出来
               2.关键词不在同一个run

             */
            String betweenTwoKeyText = "";
            //关键词在同一个run中使用
            String keyTemp = "";
            //用作更新firstindex
            int indexTemp = searchBegin;
            //为二分查找开始和结束 runpos 提供初始位置
            int originalStartRunPos = 0, originalEndRunPos = 0;
            while (paraTemp.indexOf(keyWord) >= 0) {
                int firstIndex = paraText.indexOf(keyWord, indexTemp);
                indexTemp = firstIndex + keyWord.length();
                int nextFirstIndex = paraText.indexOf(keyWord, indexTemp);
                originalStartRunPos = binaryFind(lengthList, originalStartRunPos, firstIndex + 1);
                originalEndRunPos = binaryFind(lengthList, originalEndRunPos, firstIndex + keyWord.length());
                startRunPos = originalStartRunPos + runPlus;
                endRunPos = originalEndRunPos + runPlus;
                //经过新增和删除run, paragraph中run的个数已经发生改变
                String startRunText = xwpfParagraph.getRuns().get(startRunPos).getText(0);
                String endRunText = xwpfParagraph.getRuns().get(endRunPos).getText(0);
                //把初始run中关键词以前的字符取出来，单独作为run创建，格式为原格式，不标红
                String runBeforeKeyText = "";
                //初始run中关键词部分取出来
                String startRunKeyText = "";
                //把结尾run关键词取出来
                String endRunKeyText = "";
                //把结尾run关键词之后的部分取出来
                String endRunBehindKeyText = "";
                //开始run和结尾 run是否位于同一个run需要单独处理
                //开始run和结束run不在同一个run
                if( endRunPos > startRunPos) {
                    //优先处理中间run
                    for (int i = startRunPos + 1; i <= endRunPos - 1; i++){
                        xwpfParagraph.getRuns().get(i).setColor("FF0000");
                    }
                    //处理初始run
                    //关键词包含整个初始run
                    if(firstIndex == 0 ){
                        xwpfParagraph.getRuns().get(startRunPos).setColor("FF0000");

                    }
                    else if (originalStartRunPos != 0 && firstIndex == lengthList.get(originalStartRunPos -1)){
                        xwpfParagraph.getRuns().get(startRunPos).setColor("FF0000");
                    }
                    else {
                        System.out.println("关键词不包含整个初始run firstIndex:" + firstIndex + " originalStartRunPos:"+ originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText: "+ endRunText + " startRunText:" + startRunText);
                        runBeforeKeyText = runLastIsKeyBefore == true ? betweenTwoKeyText : startRunText.substring(0, originalStartRunPos == 0 ? firstIndex : firstIndex - lengthList.get(originalStartRunPos - 1));
                        runLastIsKeyBefore = false;//用完后归位
                        XWPFRun beforeKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                        //经过以上处理，需要更新startRunPos endRunPos
                        startRunPos += 1;
                        endRunPos += 1;
                        beforeKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                        beforeKeyRun.setText(runBeforeKeyText);
                        startRunText = xwpfParagraph.getRuns().get(startRunPos).getText(0);
                        startRunKeyText = startRunText.substring(originalStartRunPos == 0 ? firstIndex : firstIndex - lengthList.get(originalStartRunPos - 1));
                        XWPFRun startKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                        //经过以上处理，需要更新startRunPos endRunPos
                        startRunPos += 1;
                        endRunPos += 1;
                        startKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                        startKeyRun.setText(startRunKeyText);
                        startKeyRun.setColor("FF0000");
                        //此时删掉关键词所在的run
                        xwpfParagraph.removeRun(startRunPos);
                        endRunPos -= 1;
                        startRunPos -=1;
                        //实际 run 增加了 1个
                        runPlus += 1;
                    }
                    //处理endRun
                    //关键词包含整个结尾run, lengthList需要原始pos
                    if(firstIndex + keyWord.length()-lengthList.get(originalEndRunPos- 1) == xwpfParagraph.getRuns().get(endRunPos).getText(0).length()){
                        xwpfParagraph.getRuns().get(endRunPos).setColor("FF0000");
                    }
                    else {
                        //endRunPos的尾部还包含下一个循环关键词的头部，需要单独处理
                        //int newFirstIndex = paraText.indexOf(keyWord, indexTemp);
                        if( nextFirstIndex != -1 && nextFirstIndex < lengthList.get(originalEndRunPos)){
                            runLastIsKeyBefore = true;
                            endRunKeyText = endRunText.substring(0, firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            startRunPos += 1;
                            //更新endRunPos
                            endRunPos += 1;
                            endKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endKeyRun.setText(endRunKeyText);
                            endKeyRun.setColor("FF0000");
                            betweenTwoKeyText = paraText.substring(firstIndex + keyWord.length(), nextFirstIndex);
                            runPlus += 1;

                        }else {
                            endRunKeyText = endRunText.substring(0, firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            //更新endRunPos
                            endRunPos += 1;
                            endKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endKeyRun.setText(endRunKeyText);
                            endKeyRun.setColor("FF0000");
                            endRunText = xwpfParagraph.getRuns().get(endRunPos).getText(0);
                            System.out.println("关键词不包含整个结尾run firstIndex:" + firstIndex + " originalStartRunPos:" + originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText:" + endRunText + " startRunText:" + startRunText);
                            endRunBehindKeyText = endRunText.substring(firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endBehindKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            //更新endRunPos
                            endRunPos += 1;
                            endBehindKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endBehindKeyRun.setText(endRunBehindKeyText);
                            //关键词所在的run必须都删掉才能
                            xwpfParagraph.removeRun(endRunPos);
                            //实际run增加了一个
                            runPlus += 1;
                        }
                    }
                }
                //关键词在同一个run
                else{
                    keyTemp = runKeyCount++ == 0 ? startRunText : keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length());

                    if(keyTemp.indexOf(keyWord) == -1){
                        System.out.println("keyTemp.indexOf(keyWord) == -1" + keyTemp);
                    }
                    System.out.println("关键词在同一个run firstIndex " + firstIndex + " originalStartRunPos:"+ originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText:" + endRunText + " startRunText " + startRunText);



                    runBeforeKeyText = runLastIsKeyBefore == true ? betweenTwoKeyText :  keyTemp.substring(0, keyTemp.indexOf(keyWord));

                    XWPFRun beforeKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                    //经过以上处理，需要更新startRunPos
                    startRunPos += 1;
                    endRunPos += 1;
                    beforeKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                    beforeKeyRun.setText(runBeforeKeyText);
                    runLastIsKeyBefore = false;
                    XWPFRun startKeyRun = xwpfParagraph.insertNewRun(startRunPos );
                    //再次更新startRunPos
                    startRunPos += 1;
                    endRunPos += 1;
                    startKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                    startKeyRun.setText(keyWord);
                    startKeyRun.setColor("FF0000");
                    //最后一个完整关键词
                    if(keyTemp.indexOf(keyWord) == keyTemp.lastIndexOf(keyWord)) {
                        //最后一个关键词的尾部还包含一个循环关键词的头部，需要单独处理
                        if( nextFirstIndex != -1 && nextFirstIndex  < lengthList.get(originalEndRunPos)){
                            runLastIsKeyBefore = true;
                            betweenTwoKeyText = paraText.substring(firstIndex + keyWord.length(), nextFirstIndex);
                            /*
                            String middleText = paraText.substring(firstIndex + keyWord.length(), newFirstIndex);
                            XWPFRun middleKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            middleKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            middleKeyRun.setText(middleText);
                            String nextKeyBeforeText = keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length() + newFirstIndex - firstIndex );
                            XWPFRun nextKeyBeforeRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            nextKeyBeforeRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            nextKeyBeforeRun.setText(nextKeyBeforeText);
                            nextKeyBeforeRun.setColor("FF0000");
                            String nextKeyBehindText = keyWord.substring(nextKeyBeforeText.length());
                            XWPFRun nextKeyBehindRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            nextKeyBehindRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos + 1).getCTR().getRPr());
                            nextKeyBehindRun.setText(nextKeyBehindText);
                            nextKeyBehindRun.setColor("FF0000");
                            indexTemp = newFirstIndex + keyWord.length();//更新下次查找起始处，关键代码
                            xwpfParagraph.removeRun(startRunPos);
                            runKeyCount = 0;//同一个run的关键词已处理完毕，标志位归零
                            runPlus += 2;

                             */


                        }else {
                            String behindKeyText = keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length());
                            XWPFRun behindKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                            //经过以上处理，需要更新startRunPos
                            startRunPos += 1;
                            endRunPos += 1;
                            behindKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            behindKeyRun.setText(behindKeyText);
                            xwpfParagraph.removeRun(startRunPos);
                            runKeyCount = 0;//同一个run的关键词已处理完毕，标志位归零
                        }
                    }
                    runPlus += 2;
                }
                paraTemp = paraText.substring(firstIndex + keyWord.length());
                if(onlyRedFirst){
                    break;
                }
            }
        }
    }
    private void redTextOnePara(XWPFParagraph xwpfParagraph, String keyWord, int searchBegin){
        String paraText = xwpfParagraph.getText().replaceAll("\\r|\\n", "");
        List<XWPFRun> runs = xwpfParagraph.getRuns();
        String paraTemp = paraText;
        if (paraTemp.contains(keyWord) && keyWord.length()!=0) {
            /*
            if(paraTemp.contains("rd 转换成 html ，再通过jsp来"))
                System.out.println("xwpfParagraph.getParagraphText() paraText:" + paraText);

             */
            int lentmp = 0;
            List<Integer> lengthList = new ArrayList<Integer>();
            List<String> runsTextList = new ArrayList<String>();
            for(int i = 0; i < runs.size(); i++){
                lentmp += runs.get(i).getText(runs.get(i).getTextPosition()) == null ? 0 : runs.get(i).getText(0).length();
                lengthList.add(lentmp);
                runsTextList.add(runs.get(i).getText(0));
            }

            for (int len = 0; len < lengthList.size(); len++)
                System.out.println(("lengthLis runsTextList: " + len + " 长度: " + lengthList.get(len)) +"  text:" + runsTextList.get(len));


            /*
            if(keyWord.equals("rd 转换成 html ，再通过jsp来")){
                System.out.println("xwpfParagraph.getText() paraText:" + xwpfParagraph.getText());
                for (int len = 0; len < lengthList.size(); len++)
                    System.out.println(("lengthLis runsTextList: " + len + " 长度: " + lengthList.get(len)) +"  text:" + runsTextList.get(len));

            }

             */
            //定义关键字所在初始Run和结束Run序号
            int startRunPos = 0, endRunPos = 0;
            //由于分割开始run和结束run标红新增的run，为了及时更新startRunPos和 endRunPos
            int runPlus = 0;
            int runKeyCount = 0;
            //某个run的结尾字符是下一个关键词的头部标志
            Boolean runLastIsKeyBefore = false;//
            /*无论关键词是否在同一个run，关键词之前的文本和之后的文本都要根据情况做处理，而不是简单新增和删除
               1.关键词在同一个run，
               1.1处理本run中第一个关键词，
               1.2处理完本run最后一个关键词后，下一个关键词的头部依然在本run
               此时处理到关键词结束就停止处理，以后的字符交给下一个循环处理，且本次循环不删除当前run，
               此时用betweenTwoKeyText把位于两个key之间的文本提前取出来
               2.关键词不在同一个run

             */
            String betweenTwoKeyText = "";
            //关键词在同一个run中使用
            String keyTemp = "";
            //用作更新firstindex
            int indexTemp = searchBegin;
            //为二分查找开始和结束 runpos 提供初始位置
            int originalStartRunPos = 0, originalEndRunPos = 0;
            while (paraTemp.indexOf(keyWord) >= 0) {
                int firstIndex = paraText.indexOf(keyWord, indexTemp);
                indexTemp = firstIndex + keyWord.length();
                int nextFirstIndex = paraText.indexOf(keyWord, indexTemp);
                originalStartRunPos = binaryFind(lengthList, originalStartRunPos, firstIndex + 1);
                originalEndRunPos = binaryFind(lengthList, originalEndRunPos, firstIndex + keyWord.length());
                startRunPos = originalStartRunPos + runPlus;
                endRunPos = originalEndRunPos + runPlus;
                //经过新增和删除run, paragraph中run的个数已经发生改变
                String startRunText = xwpfParagraph.getRuns().get(startRunPos).getText(0);
                String endRunText = xwpfParagraph.getRuns().get(endRunPos).getText(0);
                //把初始run中关键词以前的字符取出来，单独作为run创建，格式为原格式，不标红
                String runBeforeKeyText = "";
                //初始run中关键词部分取出来
                String startRunKeyText = "";
                //把结尾run关键词取出来
                String endRunKeyText = "";
                //把结尾run关键词之后的部分取出来
                String endRunBehindKeyText = "";
                //开始run和结尾 run是否位于同一个run需要单独处理
                //开始run和结束run不在同一个run
                if( endRunPos > startRunPos) {
                    //优先处理中间run
                    for (int i = startRunPos + 1; i <= endRunPos - 1; i++){
                        xwpfParagraph.getRuns().get(i).setColor("FF0000");
                    }
                    //处理初始run
                    //关键词包含整个初始run
                    if(firstIndex == 0 ){
                        xwpfParagraph.getRuns().get(startRunPos).setColor("FF0000");

                    }
                    else if (originalStartRunPos != 0 && firstIndex == lengthList.get(originalStartRunPos -1)){
                        xwpfParagraph.getRuns().get(startRunPos).setColor("FF0000");
                    }
                    else {
                        System.out.println("关键词不包含整个初始run firstIndex:" + firstIndex + " originalStartRunPos:"+ originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText: "+ endRunText + " startRunText:" + startRunText);
                        runBeforeKeyText = runLastIsKeyBefore == true ? betweenTwoKeyText : startRunText.substring(0, originalStartRunPos == 0 ? firstIndex : firstIndex - lengthList.get(originalStartRunPos - 1));
                        runLastIsKeyBefore = false;//用完后归位
                        XWPFRun beforeKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                        //经过以上处理，需要更新startRunPos endRunPos
                        startRunPos += 1;
                        endRunPos += 1;
                        beforeKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                        beforeKeyRun.setText(runBeforeKeyText);
                        startRunText = xwpfParagraph.getRuns().get(startRunPos).getText(0);
                        startRunKeyText = startRunText.substring(originalStartRunPos == 0 ? firstIndex : firstIndex - lengthList.get(originalStartRunPos - 1));
                        XWPFRun startKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                        //经过以上处理，需要更新startRunPos endRunPos
                        startRunPos += 1;
                        endRunPos += 1;
                        startKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                        startKeyRun.setText(startRunKeyText);
                        startKeyRun.setColor("FF0000");
                        //此时删掉关键词所在的run
                        xwpfParagraph.removeRun(startRunPos);
                        endRunPos -= 1;
                        startRunPos -=1;
                        //实际 run 增加了 1个
                        runPlus += 1;
                    }
                    //处理endRun
                    //关键词包含整个结尾run, lengthList需要原始pos
                    if(firstIndex + keyWord.length()-lengthList.get(originalEndRunPos- 1) == xwpfParagraph.getRuns().get(endRunPos).getText(0).length()){
                        xwpfParagraph.getRuns().get(endRunPos).setColor("FF0000");
                    }
                    else {
                        //endRunPos的尾部还包含下一个循环关键词的头部，需要单独处理
                        //int newFirstIndex = paraText.indexOf(keyWord, indexTemp);
                        if( nextFirstIndex != -1 && nextFirstIndex < lengthList.get(originalEndRunPos)){
                            runLastIsKeyBefore = true;
                            endRunKeyText = endRunText.substring(0, firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            startRunPos += 1;
                            //更新endRunPos
                            endRunPos += 1;
                            endKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endKeyRun.setText(endRunKeyText);
                            endKeyRun.setColor("FF0000");
                            betweenTwoKeyText = paraText.substring(firstIndex + keyWord.length(), nextFirstIndex);
                            runPlus += 1;

                        }else {
                            endRunKeyText = endRunText.substring(0, firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            //更新endRunPos
                            endRunPos += 1;
                            endKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endKeyRun.setText(endRunKeyText);
                            endKeyRun.setColor("FF0000");
                            endRunText = xwpfParagraph.getRuns().get(endRunPos).getText(0);
                            System.out.println("关键词不包含整个结尾run firstIndex:" + firstIndex + " originalStartRunPos:" + originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText:" + endRunText + " startRunText:" + startRunText);
                            endRunBehindKeyText = endRunText.substring(firstIndex + keyWord.length() - lengthList.get(originalEndRunPos - 1));
                            XWPFRun endBehindKeyRun = xwpfParagraph.insertNewRun(endRunPos);
                            //更新endRunPos
                            endRunPos += 1;
                            endBehindKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(endRunPos).getCTR().getRPr());
                            endBehindKeyRun.setText(endRunBehindKeyText);
                            //关键词所在的run必须都删掉才能
                            xwpfParagraph.removeRun(endRunPos);
                            //实际run增加了一个
                            runPlus += 1;
                        }
                    }
                }
                //关键词在同一个run
                else{
                    keyTemp = runKeyCount++ == 0 ? startRunText : keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length());

                    if(keyTemp.indexOf(keyWord) == -1){
                        System.out.println("keyTemp.indexOf(keyWord) == -1" + keyTemp);
                    }
                    System.out.println("关键词在同一个run firstIndex " + firstIndex + " originalStartRunPos:"+ originalStartRunPos + " originalEndRunPos:" + originalEndRunPos + " startRunPos:" + startRunPos + " endRunPos:" + endRunPos + " 关键词key:" + keyWord + " endRunText:" + endRunText + " startRunText " + startRunText);


                    runBeforeKeyText = runLastIsKeyBefore == true ? betweenTwoKeyText :  keyTemp.substring(0, keyTemp.indexOf(keyWord));

                    XWPFRun beforeKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                    //经过以上处理，需要更新startRunPos
                    startRunPos += 1;
                    endRunPos += 1;
                    beforeKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                    beforeKeyRun.setText(runBeforeKeyText);
                    runLastIsKeyBefore = false;
                    XWPFRun startKeyRun = xwpfParagraph.insertNewRun(startRunPos );
                    //再次更新startRunPos
                    startRunPos += 1;
                    endRunPos += 1;
                    startKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                    startKeyRun.setText(keyWord);
                    startKeyRun.setColor("FF0000");
                    //最后一个完整关键词
                    if(keyTemp.indexOf(keyWord) == keyTemp.lastIndexOf(keyWord)) {
                        //最后一个关键词的尾部还包含一个循环关键词的头部，需要单独处理
                        if( nextFirstIndex != -1 && nextFirstIndex  < lengthList.get(originalEndRunPos)){
                            runLastIsKeyBefore = true;
                            betweenTwoKeyText = paraText.substring(firstIndex + keyWord.length(), nextFirstIndex);
                            /*
                            String middleText = paraText.substring(firstIndex + keyWord.length(), newFirstIndex);
                            XWPFRun middleKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            middleKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            middleKeyRun.setText(middleText);
                            String nextKeyBeforeText = keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length() + newFirstIndex - firstIndex );
                            XWPFRun nextKeyBeforeRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            nextKeyBeforeRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            nextKeyBeforeRun.setText(nextKeyBeforeText);
                            nextKeyBeforeRun.setColor("FF0000");
                            String nextKeyBehindText = keyWord.substring(nextKeyBeforeText.length());
                            XWPFRun nextKeyBehindRun = xwpfParagraph.insertNewRun(startRunPos);
                            startRunPos += 1;
                            endRunPos += 1;
                            nextKeyBehindRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos + 1).getCTR().getRPr());
                            nextKeyBehindRun.setText(nextKeyBehindText);
                            nextKeyBehindRun.setColor("FF0000");
                            indexTemp = newFirstIndex + keyWord.length();//更新下次查找起始处，关键代码
                            xwpfParagraph.removeRun(startRunPos);
                            runKeyCount = 0;//同一个run的关键词已处理完毕，标志位归零
                            runPlus += 2;

                             */


                        }else {
                            String behindKeyText = keyTemp.substring(keyTemp.indexOf(keyWord) + keyWord.length());
                            XWPFRun behindKeyRun = xwpfParagraph.insertNewRun(startRunPos);
                            //经过以上处理，需要更新startRunPos
                            startRunPos += 1;
                            endRunPos += 1;
                            behindKeyRun.getCTR().setRPr(xwpfParagraph.getRuns().get(startRunPos).getCTR().getRPr());
                            behindKeyRun.setText(behindKeyText);
                            xwpfParagraph.removeRun(startRunPos);
                            runKeyCount = 0;//同一个run的关键词已处理完毕，标志位归零
                        }
                    }
                    runPlus += 2;
                }
                paraTemp = paraText.substring(firstIndex + keyWord.length());
            }
        }
    }

    /**
     * 二分查找关键词出现的初始run
     * @param list
     * @param begin
     * @param key
     * @return run下标
     */
    private int binaryFind(List<Integer> list, int begin, int key){
        int length = list.size();
        int low = begin, high = length-1;
        int m =0;
        while(low <= high){
            m = (low + high)/2;
            if(list.get(m) >= key) {
                if (m >= 1 && list.get(m - 1) < key)
                    return m;
                high = m - 1;
            }
            else
                low = m + 1;

        }
        return m;
    }
    private  String newFileName(String leftFilePath, String rightFilePath, String repeatWord){
        File leftFile = new File(leftFilePath);
        File rightFile = new File(rightFilePath);
        File newFile = null;
        try {
            //如果参数是目录直接返回null
            if (leftFile.isDirectory() || rightFile.isDirectory()) {
                System.out.println("传参是目录，不是文件！！");
                return null;
            }
            String leftFileName = leftFile.getName();
            String rightFileName = rightFile.getName();
            String extendName = leftFileName.substring(leftFileName.lastIndexOf("."));
            String leftFileNameNoExtend = leftFileName.substring(0, leftFileName.lastIndexOf("."));
            String rightFileNameNoExtend = rightFileName.substring(0, rightFileName.lastIndexOf("."));
            //设置文件存储路径，可以存放在你想要指定的路径里面
            String parentDir = leftFile.getParent();
            String rootPath = parentDir + File.separator + newDocDirName;
            // 新文件名
            StringBuffer newFileName = new StringBuffer(leftFileNameNoExtend);
            String newDocxName = newFileName.append("_" + ((repeatWord.length() > 5) ? repeatWord.substring(0, 5 ) :repeatWord) + "_").append(rightFileNameNoExtend).append(UUID.randomUUID().toString().substring(0,4)).append(extendName).toString();
            //新文件
            newFile = new File(rootPath + File.separator + newDocxName);
            //判断文件目录是否存在
            if (!newFile.getParentFile().exists()) {
                //如果目标文件所在的目录不存在，则创建父目录
                newFile.getParentFile().mkdirs();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        return newFile.getPath();
    }
    /**
     * 将修改后的文件另存为
     * @param filePath
     * @param doc
     * @throws Exception
     */
    private void saveToFile(String filePath, XWPFDocument doc) throws Exception {
        try (FileOutputStream out = new FileOutputStream(new File(filePath))) {
            doc.write(out);
            close(out);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 关闭输入流
     * @param is
     */
    private void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     * @param os
     */
    private void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
